package com.atguigu.service.impl;

import com.atguigu.cache.ShopCache;
import com.atguigu.constant.RedisConst;
import com.atguigu.entity.ProductSalePropertyKey;
import com.atguigu.entity.SkuImage;
import com.atguigu.entity.SkuInfo;
import com.atguigu.exception.SleepUtils;
import com.atguigu.mapper.ProductSalePropertyKeyMapper;
import com.atguigu.mapper.SkuSalePropertyValueMapper;
import com.atguigu.service.SkuDetailService;
import com.atguigu.service.SkuImageService;
import com.atguigu.service.SkuInfoService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
public class SkuDetailServiceImpl implements SkuDetailService {

    @Autowired
    private SkuInfoService skuInfoService;
    @Autowired
    private SkuImageService skuImageService;
    @Autowired
    private SkuSalePropertyValueMapper skuSalePropertyValueMapper;
    @Autowired
    private ProductSalePropertyKeyMapper productSalePropertyKeyMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RBloomFilter skuRBloomFilter;

    @Override
    public SkuInfo getSkuInfo(Long skuId) {
//        SkuInfo skuInfo = getSkuInfoFromRedis(skuId);
//        SkuInfo skuInfo = getSkuInfoFromRedisWithTreadLocal(skuId);
//        SkuInfo skuInfo = getSkuInfoFromRedisson(skuId);
          SkuInfo skuInfo = getSkuInfoFromDB(skuId);

        return skuInfo;
    }

    private SkuInfo getSkuInfoFromRedisson(Long skuId) {
        //sku:24:info
        String cacheKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;
        //从缓存中查询数据
        SkuInfo skuInfoFromRedis = (SkuInfo) redisTemplate.opsForValue().get(cacheKey);
        if (skuInfoFromRedis == null) {
            String lockKey="key-"+skuId;
            RLock lock = redissonClient.getLock(lockKey);
            lock.lock();
            try {
                boolean flag = skuRBloomFilter.contains(skuId);
                SkuInfo skuInfoDB=null;
                if (flag) {
                    skuInfoDB=getSkuInfoFromDB(skuId);
                    //把数据放入缓存
                    redisTemplate.opsForValue().set(cacheKey,skuInfoDB, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
                }
                return skuInfoDB;
            } finally {
                lock.unlock();
            }
        }
        return skuInfoFromRedis;
    }

    //利用lua脚本+redis+ThreadLocal 实现分布式锁
    ThreadLocal<String>threadLocal=new ThreadLocal<>();
    private SkuInfo getSkuInfoFromRedisWithTreadLocal(Long skuId) {

        //sku:24:info
        String cacheKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;
        //从缓存中查询数据
        SkuInfo skuInfoFromRedis = (SkuInfo) redisTemplate.opsForValue().get(cacheKey);
        if (skuInfoFromRedis == null) {

            String token = threadLocal.get();
            //定义一个锁的名称，不同产品查询可以同时进行
            String lockKey="key-"+skuId;
            boolean acquireLock = false;
            if (token==null) {
                token = UUID.randomUUID().toString();
                //利用redis的setnx命令,当设置的key不存在的时候 setnx会生效
                acquireLock = redisTemplate.opsForValue().setIfAbsent(lockKey, token, 1, TimeUnit.SECONDS);
            } else {
                //代表已经拿到锁
                //String lock = (String) redisTemplate.opsForValue().get("lock");
                //token赋值
                //token=lock;
                acquireLock=true;
            }

            if (acquireLock) {
                //拿到锁 执行业务
                SkuInfo skuInfoDB = doBusiness(skuId, cacheKey);
                //业务做完删除锁
                String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
                //把lua脚本封装到redisScript中
                redisScript.setScriptText(luaScript);
                //设置脚本运行完成后 返回的数据类型
                redisScript.setResultType(Long.class);
                //执行脚本
                redisTemplate.execute(redisScript, Arrays.asList(lockKey), token);
                //放在内存泄露
                threadLocal.remove();
                return skuInfoDB;
            } else {
                //如果没有拿到锁
                for (;;){
                    SleepUtils.millis(50);
                    boolean retryAcquireLock = redisTemplate.opsForValue().setIfAbsent(lockKey, token, 1, TimeUnit.SECONDS);
                    if (retryAcquireLock) {
                        //拿到锁后就不需要自旋，把锁标记一下
                        threadLocal.set(token);
                        break;
                    }
                }
                return getSkuInfoFromRedisWithTreadLocal(skuId);
            }
        }
        return skuInfoFromRedis;
    }

    private SkuInfo getSkuInfoFromRedis(Long skuId) {
        //sku:24:info
        String cacheKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;
        //从缓存中查询数据
        SkuInfo skuInfoFromRedis = (SkuInfo) redisTemplate.opsForValue().get(cacheKey);
        if (skuInfoFromRedis == null) {
            return doBusiness(skuId, cacheKey);
        }
        return skuInfoFromRedis;
    }
    //业务，把查询的数据库数据放入redis缓存中
    private SkuInfo doBusiness(Long skuId, String cacheKey) {
        SkuInfo skuInfoDB = getSkuInfoFromDB(skuId);
        //把数据放入缓存
        redisTemplate.opsForValue().set(cacheKey,skuInfoDB, RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
        return skuInfoDB;
    }

    private SkuInfo getSkuInfoFromDB(Long skuId) {
        //1.查询商品的基本信息
        SkuInfo skuInfo = skuInfoService.getById(skuId);
        //2.查询商品图片信息
        if (skuInfo != null) {
            QueryWrapper<SkuImage> query = new QueryWrapper<>();
            query.eq("sku_id",skuId);
            List<SkuImage> list = skuImageService.list(query);
            skuInfo.setSkuImageList(list);
        }
        return skuInfo;
    }

    @Override
    public Map<Object,Object> getSalePropertyAndSkuIdMapping(Long productId) {
        Map<Object,Object> salePropertyAndSkuIdMap = new HashMap<>();
        List<Map> mapList = skuSalePropertyValueMapper.getSalePropertyAndSkuIdMapping(productId);
        for (Map map : mapList) {
            salePropertyAndSkuIdMap.put(map.get("sale_property_value_id"),map.get("sku_id"));
        }
        return salePropertyAndSkuIdMap;
    }

    @Override
    public List<ProductSalePropertyKey> getSpuSalePropertyAndSelected(Long productId, Long skuId) {
        List<ProductSalePropertyKey>list=productSalePropertyKeyMapper.getSpuSalePropertyAndSelected(productId,skuId);
        return list;
    }
}
